/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * SimulationPanel.java
 *
 * Created on 14 mars 2012, 22:09:51
 */
package kuti.grp5.ui;

import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.border.Border;
import javax.swing.filechooser.FileNameExtensionFilter;
import kuti.EWorldElementType;
import kuti.IChargingStation;
import kuti.IKuti;
import kuti.IMagneticObstacle;
import kuti.IObstacle;
import kuti.ISimulParams;
import kuti.IWorldElement;
import kuti.IWorldManager;
import kuti.grp5.Factory;
import kuti.grp5.SimulParams;
import kuti.ui.LogPanel;
import kuti.ui.UISimulationPanel;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import kuti.ui.ESimulationPanelMouseMode;

/**
 * This class contains all the necessary elements to run the simulation such as
 * Play buttons, mute / zoom fonctions, tooltips, import / export XML, etc...
 * This class receives IWorldObserver events from WorldManager.
 *
 * @author Jocelyn
 */
public class SimulationPanel extends JDialog {

    /**
     * Constructor taking 3 parameters. Sets its owner invisible.
     *
     * @param owner - Calling window (should be ParamPanel)
     * @param simParams - Simulation parameters
     * @param randomGen - true if the world must be randomly generated, false
     * otherwise
     * @param randomExpr - expression specifying how many objects are created,
     * see ParamPanel for more details
     */
    public SimulationPanel(JDialog owner, ISimulParams simParams, GenerationType genType, String randomExpr, String difficulty) {
        super(owner, false);
        this.owner = owner;
        this.simParams = simParams;
        this.difficulty = difficulty;

        owner.setVisible(false);

        initComponents();
        initMyComponents();
        setLocationRelativeTo(this);

        switch (genType) {
            case RANDOM_GEN:
                String ar[] = randomExpr.split(";");
                pMapCustomizer.setVisible(false);
                int nbKutis = Integer.parseInt(ar[0]);
                int nbStations = Integer.parseInt(ar[1]);
                int nbObs = Integer.parseInt(ar[2]);
                int nbMObs = Integer.parseInt(ar[3]);
                boolean isExact = Boolean.parseBoolean(ar[4]);
                elts = RandomWorldGenerator.generateRandomWorld(nbKutis, nbStations, nbObs, nbMObs, simParams, !isExact);
                populateWorld();
                break;

            case MANUAL_GEN:
                simOps.setdrawRadarBeams(false);
                tfSteps.setEnabled(false);
                tfStepInterval.setEnabled(false);
                btStop.setEnabled(false);
                btZoom.setEnabled(false);
                break;

            case PRESET_GEN:
                pMapCustomizer.setVisible(false);
                setDifficulty(difficulty);
                populateWorld();
                break;
        }

        wm.addChangeObserver(sim);
        setVisible(true);
    }

    /**
     * intialize the components (from the API) necessary to run the simulation,
     * adds the listeners, sets the focus to the simulation panel.
     */
    private void initMyComponents() {
        factory = new Factory(simParams);

        wm = factory.newWorldManager();

        simOps = new SimulationPanelOperations(this, wm, logs, simParams);
        sim = new UISimulationPanel(wm, simOps);

        mapCustomAdapter = new MapCreationMouseController(elts, sim, simParams, logs, sliderTau);
        sim.addMouseMotionListener(mapCustomAdapter);
        sim.addMouseListener(mapCustomAdapter);
        sim.initAnimPanel();

        spSim.setViewportView(sim);
        spSim.requestFocusInWindow();
        pLogs.add(logs);
    }

    /**
     * Only called ONCE before the simulation starts. Enables the simulation's
     * control, creates the borders and logs the world state.
     */
    private void populateWorld() {
        populate = false;
        enableSimControls(true, true);
        pMapCustomizer.setVisible(false);
        createWorldBorderObstacle(false);
        wm.populateWorld(elts, simParams);
        logs.addEntry("Début de la simulation");
        logWorldState();
    }

    /**
     * Create the borders of the world with obstacles (adds them directly in
     * "elts")
     *
     * @param magnetic - true if the obstacles must be magnetic, false
     * otherwise.
     */
    private void createWorldBorderObstacle(boolean magnetic) {

        Line2D north = new Line2D.Double(0, 0, ISimulParams.WORLD_WIDTH, 0);
        Line2D south = new Line2D.Double(0, ISimulParams.WORLD_HEIGHT, ISimulParams.WORLD_WIDTH, ISimulParams.WORLD_HEIGHT);
        Line2D west = new Line2D.Double(0, 0, 0, ISimulParams.WORLD_HEIGHT);
        Line2D east = new Line2D.Double(ISimulParams.WORLD_WIDTH, 0, ISimulParams.WORLD_WIDTH, ISimulParams.WORLD_HEIGHT);
        if (!magnetic) {
            elts.add(factory.newObstacle(north));
            elts.add(factory.newObstacle(south));
            elts.add(factory.newObstacle(west));
            elts.add(factory.newObstacle(east));
        } else {
            elts.add(factory.newMagneticObstacle(factory.newObstacle(north)));
            elts.add(factory.newMagneticObstacle(factory.newObstacle(south)));
            elts.add(factory.newMagneticObstacle(factory.newObstacle(west)));
            elts.add(factory.newMagneticObstacle(factory.newObstacle(east)));
        }
    }

    /**
     * Logs the world state in the log panel specifying the number of each
     * object.
     */
    private void logWorldState() {
        logs.addEntry("The world is composed of :");
        logs.addEntry(wm.getAllKuti().size() + " Kutis");
        logs.addEntry(wm.getAllChargingStation().size() + " Charging Stations");
        logs.addEntry(wm.getAllObstacle().size() - 4 + " Obstacles"); // minus the 4 borders
    }

    /**
     * Stops the simulation in the world manager and deactivate the simulation
     * controls
     */
    private void stopSimulation() {
        playing = false;
        wm.stopSimulation();
        logs.addEntry("Fin de la simulation");
        enableSimControls(false, false);
    }

    /**
     * Updates if necessary the pantheon according to score and difficulty
     * passed in parameters
     *
     * @param score - pts scored in the simulation
     * @param difficulty - difficulty of the simulation
     */
    private void updatePantheon(double score) {
        SAXBuilder sxb = new SAXBuilder();
        Document doc = null;
        File f = new File("pantheon.xml");
        try {
            InputStream is = getClass().getResourceAsStream("/kuti/grp5/ui/resources/pantheon.xml");
            doc = f.exists() ? sxb.build(f) : sxb.build(is);
        } catch (JDOMException ex) {
            Logger.getLogger(ParamPanel.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ParamPanel.class.getName()).log(Level.SEVERE, null, ex);
        }

        Element root = doc.getRootElement();

        List<Element> scores = root.getChildren("score");

        for (Element elt : scores) {
            String attr = elt.getAttributeValue("diff");
            if (attr.equals(difficulty)) {
                double oldScore = Double.parseDouble(elt.getAttributeValue("score"));
                if (oldScore < score) {
                    String nom;
                    DecimalFormat df = new DecimalFormat("0.000");
                    String formattedScore = df.format(wm.getScore());
                    do {
                        nom = JOptionPane.showInputDialog(this, "Nouveau record : " + formattedScore + " en " + difficulty
                                + "\nEntrez votre nom :", "Nouveau record !", JOptionPane.INFORMATION_MESSAGE);
                    } while (nom == null || nom.equals(""));
                    elt.setAttribute("date", new SimpleDateFormat("dd.MM.yyyy").format(new Date()));
                    elt.setAttribute("nom", nom);
                    elt.setAttribute("score", formattedScore);
                    try {
                        new XMLOutputter(Format.getPrettyFormat()).output(doc, new FileOutputStream(f));
                    } catch (IOException ex) {
                        Logger.getLogger(SimulationPanel.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    break;
                }
            }
        }

    }

    /**
     * Enable / Disable the following functionalities (JComponents) : Play
     * (playBtEnable) Step by Step Stop Changing steps in the textfield Changing
     * step interval in the textfield XML Export Zoom
     *
     * @param enable - true if elements (except play) must be enabled, false
     * otherwise
     * @param playBtEnable - true if play button must be enabled, false
     * otherwise
     */
    private void enableSimControls(boolean enable, boolean playBtEnable) {
        btPlay.setEnabled(playBtEnable);
        btForward.setEnabled(enable);
        btStop.setEnabled(enable);
        tfSteps.setEnabled(enable);
        tfStepInterval.setEnabled(enable);
        btExportXML.setEnabled(enable);
        btZoom.setEnabled(enable);
    }

    /**
     * This method must be called when the simulation is played for the first
     * time. It resets the radarBeams to true because not shown in manual map
     * creation. It removes the listener used in the manual map creation. It
     * launches the populateWorld if not done by the automap creation
     */
    private void firstTimePlay() {
        firstTimePlay = false;
        simOps.setdrawRadarBeams(true);
        sim.removeMouseListener(mapCustomAdapter);
        //sim.addMouseListener(new UISimulationMouseController(sim, new DraggingData(), new VisibleZone(), simOps));
        if (populate) {
            populateWorld();
        }
        wm.startSimulation();
    }

    /**
     * refreshes the score labels with 3 decimals
     */
    private void refreshScore() {
        DecimalFormat df = new DecimalFormat("0.000");
        labScore.setText("Score : " + df.format(wm.getScore()));
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        pSim = new javax.swing.JPanel();
        spSim = new javax.swing.JScrollPane();
        pSimOps = new javax.swing.JPanel();
        btPlay = new javax.swing.JToggleButton();
        btForward = new javax.swing.JButton();
        btStop = new javax.swing.JButton();
        labStepTitle = new javax.swing.JLabel();
        tfSteps = new javax.swing.JTextField();
        labScore = new javax.swing.JLabel();
        pLogs = new javax.swing.JPanel();
        pMapCustomizer = new javax.swing.JPanel();
        labKuti = new javax.swing.JLabel();
        labBorne = new javax.swing.JLabel();
        sliderTau = new javax.swing.JSlider();
        labObst = new javax.swing.JLabel();
        labMagnObst = new javax.swing.JLabel();
        btImportXML = new javax.swing.JButton();
        labShortcutsTitleMap = new javax.swing.JLabel();
        labShrtCtKuti = new javax.swing.JLabel();
        labShrtCtStation = new javax.swing.JLabel();
        labShrtCtPlayObst = new javax.swing.JLabel();
        labShrtCtMagnObst = new javax.swing.JLabel();
        pOps = new javax.swing.JPanel();
        pStepInterval = new javax.swing.JPanel();
        labStepInterval = new javax.swing.JLabel();
        tfStepInterval = new javax.swing.JTextField();
        cbShowTooltips = new javax.swing.JCheckBox();
        btExportXML = new javax.swing.JButton();
        btMute = new javax.swing.JToggleButton();
        btZoom = new javax.swing.JToggleButton();
        labShortcutsTitleOps = new javax.swing.JLabel();
        labShrtCtPlay = new javax.swing.JLabel();
        labShrtCtStep = new javax.swing.JLabel();
        labShrtCtTooltip = new javax.swing.JLabel();
        labShrtCtMute = new javax.swing.JLabel();
        labShrtCtZoom = new javax.swing.JLabel();

        setTitle("Panel de simulation");
        setMinimumSize(new java.awt.Dimension(1280, 600));
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        pSim.setPreferredSize(new Dimension(SimulParams.WORLD_WIDTH, SimulParams.WORLD_HEIGHT));
        pSim.setLayout(new javax.swing.BoxLayout(pSim, javax.swing.BoxLayout.LINE_AXIS));
        pSim.add(spSim);

        getContentPane().add(pSim, java.awt.BorderLayout.CENTER);

        pSimOps.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));

        btPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/play_icon.png"))); // NOI18N
        btPlay.setFocusable(false);
        btPlay.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btPlayActionPerformed(evt);
            }
        });
        pSimOps.add(btPlay);

        btForward.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/forward_icon.png"))); // NOI18N
        btForward.setFocusable(false);
        btForward.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btForwardActionPerformed(evt);
            }
        });
        pSimOps.add(btForward);

        btStop.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/stop_icon.png"))); // NOI18N
        btStop.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btStopActionPerformed(evt);
            }
        });
        pSimOps.add(btStop);

        labStepTitle.setText("N° Step :");
        pSimOps.add(labStepTitle);

        tfSteps.setColumns(3);
        tfSteps.setText("0");
        tfSteps.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                tfStepsKeyPressed(evt);
            }
        });
        pSimOps.add(tfSteps);

        labScore.setFont(new java.awt.Font("Lucida Grande", 1, 13));
        labScore.setText("Score : 0");
        pSimOps.add(labScore);

        getContentPane().add(pSimOps, java.awt.BorderLayout.NORTH);

        pLogs.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
        pLogs.setPreferredSize(new java.awt.Dimension(992, 100));
        pLogs.setLayout(new javax.swing.BoxLayout(pLogs, javax.swing.BoxLayout.LINE_AXIS));
        getContentPane().add(pLogs, java.awt.BorderLayout.SOUTH);

        pMapCustomizer.setBackground(new java.awt.Color(255, 255, 255));
        pMapCustomizer.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
        pMapCustomizer.setLayout(new javax.swing.BoxLayout(pMapCustomizer, javax.swing.BoxLayout.Y_AXIS));

        labKuti.setBackground(new java.awt.Color(255, 255, 255));
        labKuti.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        labKuti.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/kuti_icon.png"))); // NOI18N
        labKuti.setToolTipText("Kuti");
        labKuti.setAlignmentX(0.5F);
        labKuti.setOpaque(true);
        labKuti.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                labKutiMouseClicked(evt);
            }
        });
        pMapCustomizer.add(labKuti);

        labBorne.setBackground(new java.awt.Color(255, 255, 255));
        labBorne.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        labBorne.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/borne_icon.png"))); // NOI18N
        labBorne.setToolTipText("Borne de recharge");
        labBorne.setAlignmentX(0.5F);
        labBorne.setOpaque(true);
        labBorne.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                labBorneMouseClicked(evt);
            }
        });
        pMapCustomizer.add(labBorne);

        sliderTau.setMajorTickSpacing(20);
        sliderTau.setMinorTickSpacing(10);
        sliderTau.setPaintLabels(true);
        sliderTau.setPaintTicks(true);
        sliderTau.setSnapToTicks(true);
        sliderTau.setPreferredSize(new java.awt.Dimension(150, 38));
        pMapCustomizer.add(sliderTau);

        labObst.setBackground(new java.awt.Color(255, 255, 255));
        labObst.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/obstacle_icon.png"))); // NOI18N
        labObst.setToolTipText("Obstacle standard");
        labObst.setAlignmentX(0.5F);
        labObst.setOpaque(true);
        labObst.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                labObstMouseClicked(evt);
            }
        });
        pMapCustomizer.add(labObst);

        labMagnObst.setBackground(new java.awt.Color(255, 255, 255));
        labMagnObst.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/magnet_icon.jpg"))); // NOI18N
        labMagnObst.setToolTipText("Obstacle magnétique");
        labMagnObst.setAlignmentX(0.5F);
        labMagnObst.setOpaque(true);
        labMagnObst.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                labMagnObstMouseClicked(evt);
            }
        });
        pMapCustomizer.add(labMagnObst);

        btImportXML.setText("<XML Imp>");
        btImportXML.setAlignmentX(0.5F);
        btImportXML.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btImportXMLActionPerformed(evt);
            }
        });
        pMapCustomizer.add(btImportXML);

        labShortcutsTitleMap.setFont(new java.awt.Font("Lucida Grande", 1, 13));
        labShortcutsTitleMap.setText("Shortcuts");
        labShortcutsTitleMap.setAlignmentX(0.5F);
        pMapCustomizer.add(labShortcutsTitleMap);

        labShrtCtKuti.setText("Kuti : Shift + K");
        labShrtCtKuti.setAlignmentX(0.5F);
        pMapCustomizer.add(labShrtCtKuti);

        labShrtCtStation.setText("Station : Shift + S");
        labShrtCtStation.setAlignmentX(0.5F);
        pMapCustomizer.add(labShrtCtStation);

        labShrtCtPlayObst.setText("Obstacle : Shift + O");
        labShrtCtPlayObst.setAlignmentX(0.5F);
        pMapCustomizer.add(labShrtCtPlayObst);

        labShrtCtMagnObst.setText("Magn. Obstacle : Shift + M");
        labShrtCtMagnObst.setAlignmentX(0.5F);
        pMapCustomizer.add(labShrtCtMagnObst);

        getContentPane().add(pMapCustomizer, java.awt.BorderLayout.WEST);

        pOps.setBackground(new java.awt.Color(255, 255, 255));
        pOps.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED));
        pOps.setPreferredSize(new java.awt.Dimension(200, 100));
        pOps.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 100, 5));

        pStepInterval.setBackground(new java.awt.Color(255, 255, 255));

        labStepInterval.setText("Step interval (ms) :");
        pStepInterval.add(labStepInterval);

        tfStepInterval.setColumns(3);
        tfStepInterval.setText("10");
        tfStepInterval.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyReleased(java.awt.event.KeyEvent evt) {
                tfStepIntervalKeyReleased(evt);
            }
        });
        pStepInterval.add(tfStepInterval);

        pOps.add(pStepInterval);

        cbShowTooltips.setSelected(true);
        cbShowTooltips.setText("Show Tooltips");
        cbShowTooltips.setAlignmentX(0.5F);
        cbShowTooltips.setFocusable(false);
        cbShowTooltips.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                cbShowTooltipsMouseClicked(evt);
            }
        });
        pOps.add(cbShowTooltips);

        btExportXML.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/xml.png"))); // NOI18N
        btExportXML.setAlignmentX(0.5F);
        btExportXML.setFocusable(false);
        btExportXML.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btExportXMLActionPerformed(evt);
            }
        });
        pOps.add(btExportXML);

        btMute.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/unmuted.png"))); // NOI18N
        btMute.setAlignmentX(0.5F);
        btMute.setFocusable(false);
        btMute.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btMuteActionPerformed(evt);
            }
        });
        pOps.add(btMute);

        btZoom.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/zoom.png"))); // NOI18N
        btZoom.setAlignmentX(0.5F);
        btZoom.setFocusable(false);
        btZoom.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btZoomActionPerformed(evt);
            }
        });
        pOps.add(btZoom);

        labShortcutsTitleOps.setFont(new java.awt.Font("Lucida Grande", 1, 13));
        labShortcutsTitleOps.setText("Shortcuts");
        pOps.add(labShortcutsTitleOps);

        labShrtCtPlay.setText("Play/Pause : <Space>");
        pOps.add(labShrtCtPlay);

        labShrtCtStep.setText("Step forward : <N>");
        pOps.add(labShrtCtStep);

        labShrtCtTooltip.setText("Show/Hide Tooltips : <T>");
        pOps.add(labShrtCtTooltip);

        labShrtCtMute.setText("Mute : <M>");
        pOps.add(labShrtCtMute);

        labShrtCtZoom.setText("Zoom : <Z>");
        pOps.add(labShrtCtZoom);

        getContentPane().add(pOps, java.awt.BorderLayout.EAST);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    /**
     * Handler for the "Play/Pause" button
     *
     * @param evt
     */
    private void btPlayActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btPlayActionPerformed
        btPlayAction(true);
    }//GEN-LAST:event_btPlayActionPerformed

    /**
     * Detailed handler for the "Play/Pause" buton. It must be specified if the
     * method is called from the buttonListener or by the SimOperation via the
     * shortcuts (HashMap<KeyStroke>,<Action>). Plays or pause the simulation.
     * If it's played for the first time, must call the "firstTimePlay" method.
     * Also sets the appropriate icon and enables the simulation controls
     * appropriately.
     *
     * @param comeFromBtListener
     */
    public void btPlayAction(boolean comeFromBtListener) {
        boolean selected = btPlay.isSelected();
        btPlay.setSelected(comeFromBtListener ? selected : !selected);

        if (btPlay.isSelected()) {
            URL url = getClass().getResource("/kuti/grp5/ui/resources/pause_icon.png");
            btPlay.setIcon(new javax.swing.ImageIcon(url));
            enableSimControls(false, true);
            if (firstTimePlay) {
                firstTimePlay();
            }
            playing = true;
            btPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/pause_icon.png")));
            enableSimControls(false, true);
            Thread t = new Thread(new Runnable() {

                @Override
                public void run() {
                    while (playing) {
                        doStep();
                        try {
                            Thread.sleep(stepIntervalMs);
                        } catch (InterruptedException ex) {
                            Logger.getLogger(SimulationPanel.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            });
            t.start();
        } else { // Pause
            playing = false;
            btPlay.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/play_icon.png")));
            enableSimControls(true, true);
        }
    }

    /**
     * Steps forward in the simulation only one step
     */
    private void doStep() {
        doSteps(wm.getStepNr() + 1);
    }

    /**
     * Step forward until "endStep" is achieved
     *
     * @param endStep - Step number to go to
     */
    private void doSteps(long endStep) {
        while (wm.getStepNr() < endStep) {
            wm.doStep();
        }

        refreshScore();
        tfSteps.setText(wm.getStepNr() + "");

        if (wm.getStepNr() == simParams.getMaxSimulationSteps()) {
            stopSimulation();
            simOps.setSimIsFinished(true);
            sim.keyStrokeActionMapping();
            if (!difficulty.equals("")) {
                updatePantheon(wm.getScore());
            }
        }
    }

    /**
     * Handler for the Step Forward button - see btDoStepAction for more details
     *
     * @param evt
     */
    private void btForwardActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btForwardActionPerformed
        btDoStepAction(true);
    }//GEN-LAST:event_btForwardActionPerformed

    /**
     * Step forward in the simulation. Simulation can be started by this action,
     * so firstTimePlay must be called.
     *
     * @param comeFromBtListener - True if it comes from the button listener,
     * false otherwise.
     */
    public void btDoStepAction(boolean comeFromBtListener) {
        if (!btForward.isEnabled()) {
            return;
        }

        boolean selected = btForward.isSelected();
        btForward.setSelected(comeFromBtListener ? selected : !selected);

        if (firstTimePlay) {
            firstTimePlay();
        }
        doStep();
    }

    /**
     * Handler for the Steps Number textfield. If Enter is pressed, the
     * simulation goes to this amount of steps.
     *
     * @param evt
     */
    private void tfStepsKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_tfStepsKeyPressed
        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
            doSteps(Long.parseLong(tfSteps.getText()));
        }
    }//GEN-LAST:event_tfStepsKeyPressed

    /**
     * Handler for the Kuti Icon in the manual map creation Specifies to the
     * MapCreator that the current elt to add is a Kuti
     *
     * @param evt
     */
    private void labKutiMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_labKutiMouseClicked
        setCurrentCustomElt(EWorldElementType.KUTI);
    }//GEN-LAST:event_labKutiMouseClicked

    /**
     * Handler for the Station Icon in the manual map creation Specifies to the
     * MapCreator that the current elt to add is a Station
     *
     * @param evt
     */
    private void labBorneMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_labBorneMouseClicked
        setCurrentCustomElt(EWorldElementType.CHARGING_STATION);
    }//GEN-LAST:event_labBorneMouseClicked

    /**
     * Handler for the Obstacle Icon in the manual map creation Specifies to the
     * MapCreator that the current elt to add is an Obstacle
     *
     * @param evt
     */
    private void labObstMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_labObstMouseClicked
        setCurrentCustomElt(EWorldElementType.OBSTACLE);
    }//GEN-LAST:event_labObstMouseClicked

    /**
     * Handler for the Magn. Obst. Icon in the manual map creation Specifies to
     * the MapCreator that the current elt to add is a Magn Obst.
     *
     * @param evt
     */
    private void labMagnObstMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_labMagnObstMouseClicked
        setCurrentCustomElt(EWorldElementType.MAGNETIC_OBSTACLE);
    }//GEN-LAST:event_labMagnObstMouseClicked

    /**
     * Specifies the mapCustomAdapter the current ElementType to add when a
     * click occurs. Also sets the borders to the appropriate icons (borders if
     * selected, null otherwise)
     *
     * @param addMode - EWorldElementType constants specifying which objects we
     * want to add
     */
    public void setCurrentCustomElt(EWorldElementType addMode) {
        mapCustomAdapter.setCustomAddMode(addMode);
        Border b = BorderFactory.createEtchedBorder();
        labMagnObst.setBorder(addMode == EWorldElementType.MAGNETIC_OBSTACLE ? b : null);
        labObst.setBorder(addMode == EWorldElementType.OBSTACLE ? b : null);
        labKuti.setBorder(addMode == EWorldElementType.KUTI ? b : null);
        labBorne.setBorder(addMode == EWorldElementType.CHARGING_STATION ? b : null);
    }

    /**
     * Handler for the Stop button, stops the simulation. (See details in
     * stopSimulation())
     *
     * @param evt
     */
    private void btStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btStopActionPerformed
        stopSimulation();
    }//GEN-LAST:event_btStopActionPerformed

    /**
     * Handler for the StepInterval textfield. Sets the interval of time in
     * milliseconds between two steps in the WorldManager.
     *
     * @param evt
     */
    private void tfStepIntervalKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_tfStepIntervalKeyReleased
        boolean valid = true;
        try {
            stepIntervalMs = Integer.parseInt(tfStepInterval.getText().trim());
        } catch (NumberFormatException ex) {
            valid = false;
            Logger.getLogger(ParamPanel.class.getName()).log(Level.SEVERE, null, ex);
        }
        if (!valid) {
            btPlay.setEnabled(false);
            btForward.setEnabled(false);
        } else {
            btPlay.setEnabled(true);
            btForward.setEnabled(true);
        }
    }//GEN-LAST:event_tfStepIntervalKeyReleased

    /**
     * Handler for the XML Import Button. Only used in the manual map creation
     * mode. Clears the lawn and the current elements and asks for an XML file
     * containing the elements to add. Calls the drawing method for each elt
     * that has a *WorldElementType* as name in the XML file.
     *
     * @param evt
     */
    private void btImportXMLActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btImportXMLActionPerformed
        elts.clear();
        sim.drawLawn();
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileFilter(new FileNameExtensionFilter("XML", "xml"));

        if (fileChooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) {
            return;
        }

        setMapXML(fileChooser.getSelectedFile(), null);
    }//GEN-LAST:event_btImportXMLActionPerformed

    /**
     * Draws the Kutis according the the XML Elements given in parameters
     * Elements must contain the center point (x,y) and the orientation
     *
     * @param kutis - XML Elements having "Kuti" as a name.
     */
    private void drawKutisFromXMLElements(List<Element> kutis) {
        for (Element e : kutis) {
            double x = Double.parseDouble(e.getAttributeValue("x"));
            double y = Double.parseDouble(e.getAttributeValue("y"));
            double orientation = Double.parseDouble(e.getAttributeValue("orientation"));
            Point2D p = new Point2D.Double(x, y);
            if (!CollisionChecker.isKutiPlacementValid(p, elts, orientation)) {
                continue;
            }
            IKuti k = factory.newKuti(p, orientation, factory.newDriver());
            sim.drawKuti(k);
            elts.add(k);
        }
        sim.repaint();
    }

    /**
     * Draws the Stations according the the XML Elements given in parameters
     * Elements must contain the top-right coordinates (x,y), the height, width
     * and the tau factor.
     *
     * @param kutis - XML Elements having "Kuti" as a name.
     */
    private void drawStationsFromXMLElements(List<Element> stations) {
        for (Element e : stations) {
            double x = Double.parseDouble(e.getAttributeValue("x"));
            double y = Double.parseDouble(e.getAttributeValue("y"));
            double height = Double.parseDouble(e.getAttributeValue("height"));
            double width = Double.parseDouble(e.getAttributeValue("width"));
            double tau = Double.parseDouble(e.getAttributeValue("tau"));
            Point2D p = new Point2D.Double(x, y);
            double radius = Math.sqrt(height * height + width * width) / 2;
            if (!CollisionChecker.isStationPlacementValid(p, radius, width, height, elts, simParams.getKDiameter())) {
                continue;
            }
            IChargingStation station = factory.newChargingStation(p, height, width, tau);
            sim.drawChargingStation(station);
            elts.add(station);
        }
        sim.repaint();
    }

    /**
     * Draws the Obstacles according the the XML Elements given in parameters
     * Elements must contain the fromPoint (x,y), the toPoint (x,y) and a
     * boolean specifying if it's magnetic or not.
     *
     * @param kutis - XML Elements having "Kuti" as a name.
     */
    private void drawObstaclesFromXMLElements(List<Element> obs) {
        for (Element e : obs) {
            double xFrom = Double.parseDouble(e.getAttributeValue("xFrom"));
            double yFrom = Double.parseDouble(e.getAttributeValue("yFrom"));
            double xTo = Double.parseDouble(e.getAttributeValue("xTo"));
            double yTo = Double.parseDouble(e.getAttributeValue("yTo"));
            boolean magnetic = Boolean.parseBoolean(e.getAttributeValue("magnetic"));
            Line2D segment = new Line2D.Double(xFrom, yFrom, xTo, yTo);
            if (!CollisionChecker.isObstaclePlacementValid(segment, elts, simParams.getKDiameter())) {
                continue;
            }
            IObstacle obstacle = factory.newObstacle(segment);
            if (magnetic) {
                IMagneticObstacle mObstacle = factory.newMagneticObstacle(obstacle);
                sim.drawObstacle(obstacle, magnetic);
                elts.add(mObstacle);
            } else {
                sim.drawObstacle(obstacle, magnetic);
                elts.add(obstacle);
            }
        }
        sim.repaint();
    }

    /**
     * Handler for the XML Export button. Exports the current map of objects
     * into a XML files (saved via the fileChooser).
     *
     * @param evt
     */
    private void btExportXMLActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btExportXMLActionPerformed

        JFileChooser fChooser = new JFileChooser();
        fChooser.setFileFilter(new FileNameExtensionFilter("XML", "xml"));

        if (fChooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION) {
            return;
        }

        Element root = new Element("map");
        Document doc = new Document(root);

        for (IWorldElement iwe : elts) {
            String magnetic = "false";
            Element e = null;
            switch (iwe.getElementType()) {
                case KUTI:
                    IKuti kuti = (IKuti) iwe;
                    e = new Element("Kuti");
                    e.setAttribute("x", String.valueOf(kuti.getCenterPosition().getX()));
                    e.setAttribute("y", String.valueOf(kuti.getCenterPosition().getY()));
                    e.setAttribute("orientation", String.valueOf(kuti.getOrientation()));
                    break;
                case CHARGING_STATION:
                    IChargingStation station = (IChargingStation) iwe;
                    e = new Element("ChargingStation");
                    e.setAttribute("x", String.valueOf(station.center().getX()));
                    e.setAttribute("y", String.valueOf(station.center().getY()));
                    e.setAttribute("height", String.valueOf(station.height()));
                    e.setAttribute("width", String.valueOf(station.width()));
                    e.setAttribute("tau", String.valueOf(station.getTau()));
                    break;
                case MAGNETIC_OBSTACLE:
                    magnetic = "true";
                case OBSTACLE:
                    IObstacle obs = (IObstacle) iwe;
                    e = new Element("Obstacle");
                    e.setAttribute("xFrom", String.valueOf(obs.segment().getX1()));
                    e.setAttribute("yFrom", String.valueOf(obs.segment().getY1()));
                    e.setAttribute("xTo", String.valueOf(obs.segment().getX2()));
                    e.setAttribute("yTo", String.valueOf(obs.segment().getY2()));
                    e.setAttribute("magnetic", magnetic);
                    break;
            }
            root.addContent(e);
        }

        File f = fChooser.getSelectedFile();
        if (!f.getAbsolutePath().endsWith(".xml")) {
            f = new File(f.getAbsolutePath() + ".xml");
        }
        try {
            new XMLOutputter(Format.getPrettyFormat()).output(doc, new FileOutputStream(f));
        } catch (Exception ex) {
            Logger.getLogger(ParamPanel.class.getName()).log(Level.SEVERE, null, ex);
        }
    }//GEN-LAST:event_btExportXMLActionPerformed

    /**
     * When the window is closed, the previous window (the owner) is set back to
     * visible. Also stops the simulation if not don.e
     *
     * @param evt
     */
    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        stopSimulation();
        owner.setVisible(true);
    }//GEN-LAST:event_formWindowClosing

    /**
     * Handler of the ToolTip ComboBox. See more details in cbToolTipAction
     *
     * @param evt
     */
    private void cbShowTooltipsMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_cbShowTooltipsMouseClicked
        cbToolTipAction(true);
    }//GEN-LAST:event_cbShowTooltipsMouseClicked

    /**
     * Shows / Hide tooltips in the simulationPanel, can come from the Shortcuts
     * map.
     *
     * @param comeFromBtListener - True if it comes from the listener, false
     * otherwise (shortcut)
     */
    public void cbToolTipAction(boolean comeFromBtListener) {
        boolean selected = cbShowTooltips.isSelected();
        cbShowTooltips.setSelected(comeFromBtListener ? selected : !selected);
        simOps.displayToolTips(cbShowTooltips.isSelected());
        simOps.setMouseMode(cbShowTooltips.isSelected() ? ESimulationPanelMouseMode.INFO : ESimulationPanelMouseMode.NOOP);
    }

    /**
     * Handler for the Mute button. More details in btMuteAction
     *
     * @param evt
     */
    private void btMuteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btMuteActionPerformed
        btMuteAction(true);
    }//GEN-LAST:event_btMuteActionPerformed

    /**
     * Mutes or not the sound in the app. Can come from the shortcutmap.
     *
     * @param comeFromBtListener - True if it comes from the listener, false
     * otherwise(shortcut)
     */
    public void btMuteAction(boolean comeFromBtListener) {
        boolean selected = btMute.isSelected();
        btMute.setSelected(comeFromBtListener ? selected : !selected);
        if (btMute.isSelected()) {
            btMute.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/muted.png")));
        } else {
            btMute.setIcon(new javax.swing.ImageIcon(getClass().getResource("/kuti/grp5/ui/resources/unmuted.png")));
        }
        simOps.setMute(btMute.isSelected());
    }

    /**
     * Handler for the Zoom button. More details in btZoomAction
     *
     * @param evt
     */
    private void btZoomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btZoomActionPerformed
        btZoomAction(true);
    }//GEN-LAST:event_btZoomActionPerformed

    /**
     * Get the correct XML sample file from the resources and call the drawing
     * method "setMapXML" to draw it on the map.
     *
     * @param evt
     */
    private void setDifficulty(String diff) {
        InputStream is = null;
        if (diff.equals("Bac à sable")) {
            is = getClass().getResourceAsStream("/kuti/grp5/ui/resources/samples/maps/bac_a_sable.xml");
        } else if (diff.equals("Facile")) {
            is = getClass().getResourceAsStream("/kuti/grp5/ui/resources/samples/maps/facile.xml");
        } else if (diff.equals("Moyen")) {
            is = getClass().getResourceAsStream("/kuti/grp5/ui/resources/samples/maps/moyen.xml");
        } else if (diff.equals("Difficile")) {
            is = getClass().getResourceAsStream("/kuti/grp5/ui/resources/samples/maps/difficile.xml");
        } else if (diff.equals("Brutal")) {
            is = getClass().getResourceAsStream("/kuti/grp5/ui/resources/samples/maps/brutal.xml");
        } else {
            return;
        }
        elts.clear();
        sim.drawLawn();
        setMapXML(null, is);
    }

    /**
     * Called by the combobox handler. Sets the map objects according to the
     * file OR inputstream in parameter.
     *
     * @param is - InpuStream pointing to an XML resource containing objects
     * information.
     * @param  f - File pointing to an XML resource containing objects
     * information.
     */
    private void setMapXML(File f, InputStream is) {
        SAXBuilder sxb = new SAXBuilder();
        Document doc = null;
        try {
            doc = f == null ? sxb.build(is) : sxb.build(f);
        } catch (JDOMException ex) {
            Logger.getLogger(ParamPanel.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ParamPanel.class.getName()).log(Level.SEVERE, null, ex);
        }
        Element root = doc.getRootElement();

        drawKutisFromXMLElements(root.getChildren("Kuti"));
        drawStationsFromXMLElements(root.getChildren("ChargingStation"));
        drawObstaclesFromXMLElements(root.getChildren("Obstacle"));
    }

    /**
     * Activates the zoom tool. Can come from the shortcutmap.
     *
     * @param comeFromBtListener - True if it comes from the listener, false
     * otherwise(shortcut)
     */
    public void btZoomAction(boolean comeFromBtListener) {
        boolean selected = btZoom.isSelected();
        btZoom.setSelected(comeFromBtListener ? selected : !selected);
        simOps.setMouseMode(btZoom.isSelected() ? ESimulationPanelMouseMode.ZOOM : ESimulationPanelMouseMode.NOOP);
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton btExportXML;
    private javax.swing.JButton btForward;
    private javax.swing.JButton btImportXML;
    private javax.swing.JToggleButton btMute;
    private javax.swing.JToggleButton btPlay;
    private javax.swing.JButton btStop;
    private javax.swing.JToggleButton btZoom;
    private javax.swing.JCheckBox cbShowTooltips;
    private javax.swing.JLabel labBorne;
    private javax.swing.JLabel labKuti;
    private javax.swing.JLabel labMagnObst;
    private javax.swing.JLabel labObst;
    private javax.swing.JLabel labScore;
    private javax.swing.JLabel labShortcutsTitleMap;
    private javax.swing.JLabel labShortcutsTitleOps;
    private javax.swing.JLabel labShrtCtKuti;
    private javax.swing.JLabel labShrtCtMagnObst;
    private javax.swing.JLabel labShrtCtMute;
    private javax.swing.JLabel labShrtCtPlay;
    private javax.swing.JLabel labShrtCtPlayObst;
    private javax.swing.JLabel labShrtCtStation;
    private javax.swing.JLabel labShrtCtStep;
    private javax.swing.JLabel labShrtCtTooltip;
    private javax.swing.JLabel labShrtCtZoom;
    private javax.swing.JLabel labStepInterval;
    private javax.swing.JLabel labStepTitle;
    private javax.swing.JPanel pLogs;
    private javax.swing.JPanel pMapCustomizer;
    private javax.swing.JPanel pOps;
    private javax.swing.JPanel pSim;
    private javax.swing.JPanel pSimOps;
    private javax.swing.JPanel pStepInterval;
    private javax.swing.JSlider sliderTau;
    private javax.swing.JScrollPane spSim;
    private javax.swing.JTextField tfStepInterval;
    private javax.swing.JTextField tfSteps;
    // End of variables declaration//GEN-END:variables
    private JTextArea taLogs = new JTextArea();
    private LogPanel logs = new LogPanel(taLogs, 10, 1);
    private Factory factory;
    private ISimulParams simParams;
    private IWorldManager wm;
    private List<IWorldElement> elts = new ArrayList<IWorldElement>();
    private SimulationPanelOperations simOps;
    private UISimulationPanel sim;
    private MapCreationMouseController mapCustomAdapter;
    private boolean firstTimePlay = true;
    private boolean populate = true;
    private JDialog owner;
    private int stepIntervalMs = 10;
    private String difficulty = "";
    private boolean playing;

    // Enum used to tell the simulation panel what type of generation we want to play with
    // MANUAL : manual generation via map editing
    // RANDOM : random gen via the random expression
    // PRESET : preset generation via the combobox in param (difficulties)
    public enum GenerationType {

        MANUAL_GEN, RANDOM_GEN, PRESET_GEN
    }
}
